import { omit } from '../utils/utils'

export type EventListenerTarget = HTMLElement | Window | Document

export interface UseEventListenerOptions<E extends keyof WindowEventMap, T extends EventListenerTarget = Document> {
  single?: AbortSignal
  /**
   * @default {Document} document
   */
  target?: T
  /**
   * addEventListener 的第三个参数
   */
  options?: boolean | EventListenerOptions
  /**
   * 执行 event.stopPropagation()
   */
  stop?: boolean
  /**
   * 执行 event.preventDefault()
   */
  prevent?: boolean
  /**
   * 事件名
   */
  event: E
  /**
   * 回调函数
   */
  listener(this: T, event: WindowEventMap[E]): void
}

export function useEventListener<E extends keyof WindowEventMap, T extends EventListenerTarget>(
  options: UseEventListenerOptions<E, T>
) {
  const { event, single, stop, prevent, listener, options: addOptions, target = document } = options

  function fn(this: T, event: WindowEventMap[E]) {
    stop && event.stopPropagation()
    prevent && event.preventDefault()
    listener.call(this, event)
  }

  target.addEventListener(event, fn as any, addOptions)

  function remove() {
    target.removeEventListener(event, fn as any, addOptions)
  }

  single?.addEventListener('abort', remove)

  return remove
}

export interface HotkeyAuxiliaryOptions {
  ctrl?: boolean
  alt?: boolean
  shift?: boolean
  command?: boolean
  repeat?: boolean
  composing?: boolean
}

export type UseHotkeyEvewnt = 'keydown' | 'keyup' | 'keypress'

type UseHotkeyEventListenerOptions<T extends EventListenerTarget = Document> = Omit<
  UseEventListenerOptions<UseHotkeyEvewnt, T>,
  'event'
> & {
  event?: UseHotkeyEvewnt
}

export interface UseHotkeyCompleteOptions<T extends EventListenerTarget = Document>
  extends HotkeyAuxiliaryOptions,
    UseHotkeyEventListenerOptions<T> {
  key?: string
}

export interface UseHotkeyCurtailOptions<T extends EventListenerTarget = Document>
  extends Omit<UseHotkeyEventListenerOptions<T>, 'listener'> {}

type EqualEntries = [prop: keyof KeyboardEvent, boolean | string][]

const auxiliaryKeyEntries: [keyof HotkeyAuxiliaryOptions, keyof KeyboardEvent][] = [
  ['ctrl', 'ctrlKey'],
  ['alt', 'altKey'],
  ['shift', 'shiftKey'],
  ['command', 'metaKey'],
  ['repeat', 'repeat'],
  ['composing', 'isComposing'],
]

function normalizeKeys(keys: string) {
  const equalEntries: EqualEntries = []
  const keyList = keys.replace(/\s/g, '').split('+')
  auxiliaryKeyEntries.forEach(([auxiliary, prop]) => {
    const index = keyList.indexOf(auxiliary)
    const value = !!~index
    equalEntries.push([prop, value])
    value && keyList.splice(index, 1)
  })

  return { equalEntries, key: keyList.length ? keyList[0] : undefined }
}

function normalizeHotkeyParameters(
  keys: UseHotkeyCompleteOptions | string,
  listener?: (event: KeyboardEvent) => void,
  options?: UseHotkeyCurtailOptions
) {
  if (typeof listener !== 'function') {
    const equalEntries: EqualEntries = auxiliaryKeyEntries.map(([auxiliary, prop]) => {
      return [prop, !!(keys as UseHotkeyCompleteOptions)[auxiliary]]
    })

    return {
      equalEntries,
      ...omit(
        keys as UseHotkeyCompleteOptions,
        auxiliaryKeyEntries.map(([key]) => key)
      ),
    }
  } else {
    return { listener, ...((options ?? {}) as UseHotkeyCurtailOptions), ...normalizeKeys(keys as string) }
  }
}

export function useHotkey<T extends EventListenerTarget = Document>(
  keys: string,
  listener: (event: KeyboardEvent) => void,
  options?: UseHotkeyCurtailOptions<T>
): () => void

export function useHotkey<T extends EventListenerTarget = Document>(options: UseHotkeyCompleteOptions<T>): () => void

export function useHotkey<T extends EventListenerTarget = Document>(
  keys: UseHotkeyCompleteOptions<T> | string,
  listener?: (event: KeyboardEvent) => void,
  options?: UseHotkeyCurtailOptions<T>
): () => void {
  const {
    key,
    equalEntries,
    stop,
    prevent,
    event = 'keydown',
    listener: callback,
    ...eventOptions
  } = normalizeHotkeyParameters(keys as UseHotkeyCompleteOptions | string, listener, options as UseHotkeyCurtailOptions)

  if (key) equalEntries.push(['key', key])

  return useEventListener({
    event,
    ...eventOptions,
    listener(event) {
      const isEqual = equalEntries.every(([prop, value]) => event[prop] === value)
      if (isEqual) {
        stop && event.stopPropagation()
        prevent && event.preventDefault()
        callback.call(this, event)
      }
    },
  })
}
